{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1eedbdb4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "import random\n",
    "from collections import Counter\n",
    "import matplotlib.pyplot as plt\n",
    "from tqdm import tqdm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "990fff25",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "OPS = ['+', '*']\n",
    "VALID_TOKENS = set(\"0123456789+*()|:=c,[] abdeilmoprstu->\")\n",
    "def generate_expression(max_depth=3, min_num=1, max_num=99):\n",
    "    if max_depth == 0:\n",
    "        return str(random.randint(min_num, max_num))\n",
    "    left = generate_expression(max_depth - 1, min_num, max_num)\n",
    "    right = generate_expression(max_depth - 1, min_num, max_num)\n",
    "    op = random.choice(OPS)\n",
    "    expr = f\"{left}{op}{right}\"\n",
    "    if random.random() < 0.3:\n",
    "        expr = f\"({expr})\"\n",
    "    return expr\n",
    "def tokenize(expr): return list(expr)\n",
    "def detokenize(tokens): return ''.join(tokens)\n",
    "def find_next_operation(tokens):\n",
    "    max_score, idx = -1, -1\n",
    "    level = 0\n",
    "    for i, t in enumerate(tokens):\n",
    "        if t == '(': level += 10\n",
    "        elif t == ')': level -= 10\n",
    "        elif t in OPS:\n",
    "            score = level + (1 if t == '*' else 0)\n",
    "            if score > max_score:\n",
    "                max_score, idx = score, i\n",
    "    return idx\n",
    "def find_matching_bracket(tokens, start_index):\n",
    "    stack = []\n",
    "    if tokens[start_index] == '(':\n",
    "        stack.append('(')\n",
    "        for i in range(start_index + 1, len(tokens)):\n",
    "            if tokens[i] == '(':\n",
    "                stack.append('(')\n",
    "            elif tokens[i] == ')':\n",
    "                stack.pop()\n",
    "                if not stack:\n",
    "                    return i\n",
    "    elif tokens[start_index] == ')':\n",
    "        stack.append(')')\n",
    "        for i in range(start_index - 1, -1, -1):\n",
    "            if tokens[i] == ')':\n",
    "                stack.append(')')\n",
    "            elif tokens[i] == '(':\n",
    "                stack.pop()\n",
    "                if not stack:\n",
    "                    return i\n",
    "    return None\n",
    "def extract_operand(tokens, op_idx, direction):\n",
    "    i = op_idx + direction\n",
    "    if 0 <= i < len(tokens) and tokens[i] in (')' if direction == -1 else '('):\n",
    "        bracket_start, bracket_end = -1, -1\n",
    "        if direction == -1:\n",
    "            bracket_end = i\n",
    "            match = find_matching_bracket(tokens, bracket_end)\n",
    "            if match is not None: bracket_start = match\n",
    "        else:\n",
    "            bracket_start = i\n",
    "            match = find_matching_bracket(tokens, bracket_start)\n",
    "            if match is not None: bracket_end = match\n",
    "        if bracket_start != -1 and bracket_end != -1:\n",
    "            calc_val = detokenize(tokens[bracket_start+1 : bracket_end])\n",
    "            return calc_val, bracket_start, bracket_end + 1\n",
    "    i = op_idx + direction\n",
    "    operand_tokens = []\n",
    "    start_idx = -1\n",
    "    while 0 <= i < len(tokens) and tokens[i].isdigit():\n",
    "        if start_idx == -1: start_idx = i\n",
    "        operand_tokens.append(tokens[i])\n",
    "        i += direction\n",
    "    if operand_tokens:\n",
    "        if direction == -1:\n",
    "            return \"\".join(reversed(operand_tokens)), i + 1, start_idx + 1\n",
    "        else:\n",
    "            return \"\".join(operand_tokens), start_idx, i\n",
    "    return None, -1, -1\n",
    "def _format_step(pos, calc_str, result):\n",
    "    arrow_result, new_carry = result % 10, result // 10\n",
    "    step = f\"pos{pos}:{calc_str}={result}->{arrow_result}\"\n",
    "    if new_carry > 0:\n",
    "        step += f\"(c{new_carry})\"\n",
    "    return step\n",
    "def split_addition(a_str, b_str):\n",
    "    max_len = max(len(a_str), len(b_str)); a_str, b_str = a_str.zfill(max_len), b_str.zfill(max_len)\n",
    "    carry, steps, res_digits = 0, [], []\n",
    "    for i in range(max_len):\n",
    "        pos_idx = max_len - 1 - i\n",
    "        a_digit, b_digit = int(a_str[pos_idx]), int(b_str[pos_idx])\n",
    "        total = a_digit + b_digit + carry; calc_str = f\"{a_digit}+{b_digit}\" + (f\"+c{carry}\" if carry > 0 else \"\")\n",
    "        steps.append(_format_step(i, calc_str, total)); res_digits.append(str(total % 10)); carry = total // 10\n",
    "    if carry > 0: res_digits.append(str(carry))\n",
    "    return \"\".join(res_digits[::-1]), steps\n",
    "def split_single_multiplication(a_str, d_char):\n",
    "    d = int(d_char); carry, steps, res_digits = 0, [], []\n",
    "    for i in range(len(a_str)):\n",
    "        pos_idx = len(a_str) - 1 - i; a_digit = int(a_str[pos_idx])\n",
    "        total = a_digit * d + carry; calc_str = f\"{a_digit}*{d}\" + (f\"+c{carry}\" if carry > 0 else \"\")\n",
    "        steps.append(_format_step(i, calc_str, total)); res_digits.append(str(total % 10)); carry = total // 10\n",
    "    if carry > 0: res_digits.append(str(carry))\n",
    "    return \"\".join(res_digits[::-1]), steps\n",
    "def split_addition_list(numbers):\n",
    "    max_len = max(len(n) for n in numbers); padded = [n.zfill(max_len) for n in numbers]\n",
    "    carry, steps, res_digits = 0, [], []\n",
    "    for i in range(max_len):\n",
    "        pos_idx = max_len - 1 - i; col_digits = [int(n[pos_idx]) for n in padded]\n",
    "        total = sum(col_digits) + carry; calc_str = '+'.join(map(str, col_digits)) + (f\"+c{carry}\" if carry > 0 else \"\")\n",
    "        steps.append(_format_step(i, calc_str, total)); res_digits.append(str(total % 10)); carry = total // 10\n",
    "    if carry > 0: res_digits.append(str(carry))\n",
    "    return \"\".join(res_digits[::-1]), steps\n",
    "def split_multiplication(a_str, b_str):\n",
    "    if len(a_str) == 1 and len(b_str) == 1:\n",
    "        res = str(int(a_str) * int(b_str)); return res, [f\"table:{a_str}*{b_str}={res}\"]\n",
    "    partials, part_steps_str = [], []\n",
    "    for i, d_char in enumerate(b_str[::-1]):\n",
    "        partial_res, steps = split_single_multiplication(a_str, d_char)\n",
    "        shifted_partial = partial_res + \"0\" * i; partials.append(shifted_partial)\n",
    "        part_steps_str.append(f\"part({d_char}*{a_str})=[{'|'.join(steps)}]={shifted_partial}\")\n",
    "    if len(partials) == 1: return partials[0], part_steps_str\n",
    "    final_res, add_steps = split_addition_list(partials)\n",
    "    sum_str = f\"sum({','.join(partials)})=[{'|'.join(add_steps)}]={final_res}\"\n",
    "    return final_res, part_steps_str + [sum_str]\n",
    "def step_by_step(expr):\n",
    "    steps = []; current_expr = expr\n",
    "    for _ in range(20):\n",
    "        if current_expr.isdigit():\n",
    "            break\n",
    "        if current_expr.startswith('(') and current_expr.endswith(')'):\n",
    "            inner = current_expr[1:-1]\n",
    "            if inner.isdigit():\n",
    "                output = f\"strip:{current_expr}={inner}|{inner}\"\n",
    "                steps.append((current_expr, output))\n",
    "                current_expr = inner\n",
    "                continue\n",
    "        tokens = tokenize(current_expr)\n",
    "        op_idx = find_next_operation(tokens)\n",
    "        if op_idx == -1: break\n",
    "        a_val, ls, le = extract_operand(tokens, op_idx, -1)\n",
    "        b_val, rs, re = extract_operand(tokens, op_idx, 1)\n",
    "        if a_val is None or b_val is None:\n",
    "            print(f\"Error: Could not extract operands for '{current_expr}'\")\n",
    "            break\n",
    "        op = tokens[op_idx]\n",
    "        start_replace_idx, end_replace_idx = min(ls, rs), max(le, re)\n",
    "        selected_part = detokenize(tokens[start_replace_idx:end_replace_idx])\n",
    "        result, digit_steps = split_addition(a_val, b_val) if op == '+' else split_multiplication(a_val, b_val)\n",
    "        inter_result = f\"{selected_part}=[{'|'.join(digit_steps)}]={result}\"\n",
    "        new_tokens = tokens[:start_replace_idx] + list(result) + tokens[end_replace_idx:]\n",
    "        remaining = detokenize(new_tokens)\n",
    "        output = f\"{inter_result}|{remaining}\"\n",
    "        steps.append((current_expr, output))\n",
    "        current_expr = remaining\n",
    "    return steps\n",
    "class DynamicEquationDataset(Dataset):\n",
    "    def __init__(self, epoch_size=50000, max_len=128):\n",
    "        self.chars = sorted(list(VALID_TOKENS)) + ['<SOS>', '<EOS>']\n",
    "        self.char2idx = {c: i+1 for i, c in enumerate(self.chars)}; self.char2idx['<PAD>'] = 0\n",
    "        self.idx2char = {i: c for c, i in self.char2idx.items()}\n",
    "        self.max_len = max_len\n",
    "        self.epoch_size = epoch_size\n",
    "    def __len__(self):\n",
    "        return self.epoch_size\n",
    "    def encode(self, txt, add_sos_eos=False):\n",
    "        if add_sos_eos:\n",
    "            ids = [self.char2idx['<SOS>']] + [self.char2idx.get(c, 0) for c in txt] + [self.char2idx['<EOS>']]\n",
    "        else:\n",
    "            ids = [self.char2idx.get(c, 0) for c in txt]\n",
    "        ids = ids[:self.max_len]\n",
    "        padded_ids = ids + [self.char2idx['<PAD>']] * (self.max_len - len(ids))\n",
    "        return torch.tensor(padded_ids, dtype=torch.long)\n",
    "    def decode(self, ids):\n",
    "        special_ids = {self.char2idx['<PAD>'], self.char2idx['<SOS>'], self.char2idx['<EOS>']}\n",
    "        return ''.join(self.idx2char.get(i.item(), '') for i in ids if i.item() not in special_ids)\n",
    "    def __getitem__(self, idx):\n",
    "        while True:\n",
    "            expr = generate_expression()\n",
    "            steps = step_by_step(expr)\n",
    "            if steps: \n",
    "                src_txt, tgt_txt = random.choice(steps)\n",
    "                return self.encode(src_txt, add_sos_eos=False), self.encode(tgt_txt, add_sos_eos=True)\n",
    "    def compute_weights(self, sample_size=20000):\n",
    "        print(f\"Approximating token weights from a sample of {sample_size} dynamic examples...\")\n",
    "        token_counts = Counter()\n",
    "        for _ in tqdm(range(sample_size)):\n",
    "            expr = generate_expression()\n",
    "            steps = step_by_step(expr)\n",
    "            if steps:\n",
    "                _, tgt = random.choice(steps)\n",
    "                full_tgt = ['<SOS>'] + list(tgt) + ['<EOS>']\n",
    "                for char in full_tgt:\n",
    "                    token_id = self.char2idx.get(char)\n",
    "                    if token_id is not None and token_id != 0: \n",
    "                        token_counts[token_id] += 1\n",
    "        weights = torch.ones(len(self.char2idx))\n",
    "        total_tokens = sum(token_counts.values())\n",
    "        if total_tokens == 0: return weights\n",
    "        token_counts[self.char2idx['<SOS>']] *= 3\n",
    "        for token_id, count in token_counts.items():\n",
    "            weights[token_id] = total_tokens / count\n",
    "        weights[0] = 0.0\n",
    "        weights = (weights / weights.sum()) * len(self.char2idx)\n",
    "        print(\"Weights computed.\")\n",
    "        return weights\n",
    "class Seq2SeqTransformer(nn.Module):\n",
    "    def __init__(self, vocab_size, embed_size=256, num_heads=8, num_layers=4, ff_dim=512):\n",
    "        super().__init__()\n",
    "        self.embed = nn.Embedding(vocab_size, embed_size, padding_idx=0)\n",
    "        self.embed_size = embed_size\n",
    "        enc_layer = nn.TransformerEncoderLayer(d_model=embed_size, nhead=num_heads, dim_feedforward=ff_dim, batch_first=True)\n",
    "        dec_layer = nn.TransformerDecoderLayer(d_model=embed_size, nhead=num_heads, dim_feedforward=ff_dim, batch_first=True)\n",
    "        self.encoder = nn.TransformerEncoder(enc_layer, num_layers=num_layers)\n",
    "        self.decoder = nn.TransformerDecoder(dec_layer, num_layers=num_layers)\n",
    "        self.fc_out = nn.Linear(embed_size, vocab_size)\n",
    "    def pos_enc(self, length, device):\n",
    "        pos = torch.arange(0, length, device=device).unsqueeze(1).float()\n",
    "        i = torch.arange(0, self.embed_size, 2, device=device).float()\n",
    "        angles = pos / (10000 ** (i / self.embed_size))\n",
    "        pe = torch.zeros(length, self.embed_size, device=device)\n",
    "        pe[:, 0::2] = torch.sin(angles)\n",
    "        pe[:, 1::2] = torch.cos(angles)\n",
    "        return pe.unsqueeze(0)\n",
    "    def forward(self, src, tgt):\n",
    "        src_pad_mask = (src == 0)\n",
    "        tgt_pad_mask = (tgt == 0)\n",
    "        src_emb = self.embed(src) + self.pos_enc(src.size(1), src.device)\n",
    "        tgt_emb = self.embed(tgt) + self.pos_enc(tgt.size(1), tgt.device)\n",
    "        tgt_mask = nn.Transformer.generate_square_subsequent_mask(tgt.size(1)).to(tgt.device)\n",
    "        memory = self.encoder(src_emb, src_key_padding_mask=src_pad_mask)\n",
    "        out = self.decoder(tgt_emb, memory, tgt_mask=tgt_mask, tgt_key_padding_mask=tgt_pad_mask, memory_key_padding_mask=src_pad_mask)\n",
    "        return self.fc_out(out)\n",
    "def train_model(epochs=10, model=None, dataset=None, use_weighted_loss=True, expression_to_test=None):\n",
    "    if dataset is None:\n",
    "        dataset = DynamicEquationDataset(epoch_size=50000, max_len=128)\n",
    "    num_workers = 0 \n",
    "    loader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=num_workers) \n",
    "    if model is None:\n",
    "        model = Seq2SeqTransformer(vocab_size=len(dataset.char2idx))\n",
    "    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "    model = model.to(device)\n",
    "    optimizer = optim.Adam(model.parameters(), lr=3e-4)\n",
    "    criterion = nn.CrossEntropyLoss(ignore_index=0)\n",
    "    if use_weighted_loss:\n",
    "        weights = dataset.compute_weights().to(device)\n",
    "        criterion = nn.CrossEntropyLoss(weight=weights, ignore_index=0)\n",
    "    losses = []\n",
    "    print(f\"\\nStarting training on {device} with a dynamic dataset (num_workers={num_workers})...\")\n",
    "    for ep in range(epochs):\n",
    "        model.train()\n",
    "        total_loss=0\n",
    "        pbar = tqdm(loader, desc=f\"Epoch {ep+1}/{epochs}\")\n",
    "        for src, tgt in pbar:\n",
    "            src, tgt = src.to(device), tgt.to(device)\n",
    "            optimizer.zero_grad()\n",
    "            out = model(src, tgt[:,:-1])\n",
    "            loss = criterion(out.reshape(-1, out.size(-1)), tgt[:,1:].reshape(-1))\n",
    "            loss.backward()\n",
    "            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n",
    "            optimizer.step()\n",
    "            total_loss += loss.item()\n",
    "            pbar.set_postfix(loss=loss.item())\n",
    "        avg_loss = total_loss / len(loader)\n",
    "        losses.append(avg_loss)\n",
    "        print(f\"--- Epoch {ep+1} Finished: Average Loss={avg_loss:.4f} ---\")\n",
    "        if expression_to_test is not None:\n",
    "            predict_feedback(model, dataset, expression_to_test)\n",
    "    return model, dataset, losses\n",
    "def predict_feedback(model, dataset, expr):\n",
    "    device = next(model.parameters()).device\n",
    "    model.eval()\n",
    "    current = expr\n",
    "    print(f\"\\n[Feedback Inference Chain on '{expr}']\")\n",
    "    with torch.no_grad():\n",
    "        seen = set()\n",
    "        for i in range(10):\n",
    "            if current in seen: print(f\"Step {i+1}: Loop detected on input '{current}'. Halting.\"); break\n",
    "            seen.add(current)\n",
    "            src = dataset.encode(current, add_sos_eos=False).unsqueeze(0).to(device)\n",
    "            tgt = torch.tensor([[dataset.char2idx['<SOS>']]], dtype=torch.long, device=device)\n",
    "            for _ in range(dataset.max_len - 1):\n",
    "                out = model(src, tgt)\n",
    "                nt_id = out[:, -1, :].argmax(-1).item()\n",
    "                if nt_id == dataset.char2idx['<EOS>']:\n",
    "                    break\n",
    "                tgt = torch.cat([tgt, torch.tensor([[nt_id]], device=device)], dim=1)\n",
    "            pred = dataset.decode(tgt[0])\n",
    "            print(f\"Step {i+1}: {current} → {pred}\")\n",
    "            if '|' in pred:\n",
    "                try: _, remaining = pred.rsplit('|', 1); current = remaining.strip()\n",
    "                except ValueError: print(\"Error parsing '|'. Halting.\"); break\n",
    "            else: print(\"No '|' found in prediction. Halting.\"); break\n",
    "            if current.isdigit() or not current: print(f\"Final result reached: {current}. Halting.\"); break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f0b280bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "New vocab size: 40\n",
      "SOS id: 38 EOS id: 39\n",
      "Approximating token weights from a sample of 20000 dynamic examples...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 20000/20000 [00:07<00:00, 2575.36it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Weights computed.\n",
      "\n",
      "Starting training on cuda with a dynamic dataset (num_workers=0)...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 1/1:   0%|          | 0/782 [00:00<?, ?it/s]c:\\Users\\xchen\\anaconda3\\envs\\CX_AI\\lib\\site-packages\\torch\\nn\\functional.py:5962: UserWarning: Support for mismatched key_padding_mask and attn_mask is deprecated. Use same type for both instead.\n",
      "  warnings.warn(\n",
      "Epoch 1/1: 100%|██████████| 782/782 [07:22<00:00,  1.77it/s, loss=0.365]\n",
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_AI\\lib\\site-packages\\torch\\nn\\modules\\transformer.py:505: UserWarning: The PyTorch API of nested tensors is in prototype stage and will change in the near future. We recommend specifying layout=torch.jagged when constructing a nested tensor, as this layout receives active development, has better operator coverage, and works with torch.compile. (Triggered internally at C:\\actions-runner\\_work\\pytorch\\pytorch\\pytorch\\aten\\src\\ATen\\NestedTensorImpl.cpp:182.)\n",
      "  output = torch._nested_tensor_from_mask(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 1 Finished: Average Loss=0.7808 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → (38)*9=[part(9*38)=[pos0:8*9=18->8(c1)|pos1:3*9+c1=19->9(c1)]=198|part(9*38)=[pos0:8*9=48->8(c4)]=480|sum(198,480)=[pos0:8+0=8->8|pos1:9+8=11->1(c1)|pos2:1+4+c1=13->3(c1)]=1318]=138|(38)+38)\n",
      "Step 2: (38)+38) → (38)+38=[pos0:8+8=13->3(c1)|pos1:3+3+c1=1->1(c1)]=113|13\n",
      "Final result reached: 13. Halting.\n"
     ]
    },
    {
     "data": {
      "image/png": "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AACwDgEIAABYhwAEAACsQwACAADWIQABAADrEIAAAIB1CEAAkAb6IMlFixb5+zIAZBACEIBsTx84qwEk6dK6dWt/XxqAHIqnwQPIETTsvPPOO17bgoOD/XY9AHI2WoAA5AgadkJDQ72W4sWLm9e0NeiNN96Qe+65RwoUKCBVq1aV+fPnex2/fft2ufvuu83rJUuWlD59+siFCxe89pk9e7bUqlXLvFfZsmVl4MCBXq/HxcXJ/fffLwULFpSbb75ZPvvssyz45AAyAwEIQK4wduxYefDBB+XHH3+URx99VDp37iw7d+40r128eFEiIiJMYPruu+9k3rx58uWXX3oFHA1QAwYMMMFIw5KGm2rVqnm9x4QJE6Rjx46ybds2adOmjXmfU6dOZflnBZAB/v9DUQEg29KnXwcGBjqFChXyWiZOnGhe119lffv29TomPDzc6devn/n7m2++6RQvXty5cOGC+3V9YnaePHmc2NhYs16uXDln9OjRKV6DvseYMWPc63ou3fbFF19k+OcFkPmoAQKQIzRv3ty00ngqUaKE+++NGzf2ek3Xt27dav6uLUF169aVQoUKuV+/4447JDExUXbv3m260I4cOSItWrRI9Rpuu+0299/1XEWLFpXjx49f92cDkPUIQAByBA0cSbukMorWBaVFvnz5vNY1OGmIApDzUAMEIFfYuHFjsvVbb73V/F3/1NogrQVyWb9+veTJk0eqV68uRYoUkSpVqsiqVauy/LoB+ActQAByhPj4eImNjfXaljdvXilVqpT5uxY2N2zYUJo2bSoffvihxMTEyKxZs8xrWqw8fvx4iYqKkqefflpOnDghgwYNkq5du0pISIjZR7f37dtXypQpY0aTnT9/3oQk3Q9A7kMAApAjLFu2zAxN96StN7t27XKP0Jo7d67079/f7Pfxxx9LzZo1zWs6bH358uXyxBNPSKNGjcy6jhibOnWq+1waji5fviyvvPKKPPnkkyZYPfTQQ1n8KQFklQCthM6ydwOATKC1OAsXLpTIyEh/XwqAHIIaIAAAYB0CEAAAsA41QAByPHryAaQXLUAAAMA6BCAAAGAdAhAAALAOAQgAAFiHAAQAAKxDAAIAANYhAAEAAOsQgAAAgHUIQAAAwDr/D7yTVlkGB2MzAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "dataset = DynamicEquationDataset(epoch_size=50000, max_len=512)\n",
    "print(\"New vocab size:\", len(dataset.char2idx))\n",
    "print(\"SOS id:\", dataset.char2idx['<SOS>'], \"EOS id:\", dataset.char2idx['<EOS>'])\n",
    "expr_to_test = \"(38+5)*9\"\n",
    "fb_model, _, fb_losses = train_model(epochs=1, dataset=dataset, expression_to_test=expr_to_test)\n",
    "plt.plot(fb_losses)\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Average Loss\")\n",
    "plt.title(\"Training Loss Curve\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "571609c4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Approximating token weights from a sample of 20000 dynamic examples...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 20000/20000 [00:08<00:00, 2486.29it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Weights computed.\n",
      "\n",
      "Starting training on cuda with a dynamic dataset (num_workers=0)...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 1/10: 100%|██████████| 782/782 [07:18<00:00,  1.78it/s, loss=0.00809]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 1 Finished: Average Loss=0.0127 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 2/10: 100%|██████████| 782/782 [07:19<00:00,  1.78it/s, loss=0.0145] \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 2 Finished: Average Loss=0.0107 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 3/10: 100%|██████████| 782/782 [07:19<00:00,  1.78it/s, loss=0.00954]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 3 Finished: Average Loss=0.0104 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 4/10: 100%|██████████| 782/782 [07:19<00:00,  1.78it/s, loss=0.0128] \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 4 Finished: Average Loss=0.0089 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 5/10: 100%|██████████| 782/782 [07:20<00:00,  1.78it/s, loss=0.00848]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 5 Finished: Average Loss=0.0086 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 6/10: 100%|██████████| 782/782 [07:19<00:00,  1.78it/s, loss=0.0134] \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 6 Finished: Average Loss=0.0078 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 7/10: 100%|██████████| 782/782 [07:19<00:00,  1.78it/s, loss=0.0019] \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 7 Finished: Average Loss=0.0073 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 8/10: 100%|██████████| 782/782 [07:18<00:00,  1.78it/s, loss=0.00333]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 8 Finished: Average Loss=0.0069 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 9/10: 100%|██████████| 782/782 [07:18<00:00,  1.78it/s, loss=0.00373]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 9 Finished: Average Loss=0.0064 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Epoch 10/10: 100%|██████████| 782/782 [07:18<00:00,  1.78it/s, loss=0.00793]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Epoch 10 Finished: Average Loss=0.0060 ---\n",
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    }
   ],
   "source": [
    "fb_model, _, fb_losses = train_model(epochs=10,model=fb_model,dataset=dataset, expression_to_test=expr_to_test,use_weighted_loss=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "938aa6ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → (43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    }
   ],
   "source": [
    "predict_feedback(fb_model, dataset, expr_to_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "2ad28ffc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[Feedback Inference Chain on '(38+18)+7*(15)+9*23']\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_AI\\lib\\site-packages\\torch\\nn\\functional.py:5962: UserWarning: Support for mismatched key_padding_mask and attn_mask is deprecated. Use same type for both instead.\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Step 1: (38+18)+7*(15)+9*23 → 38+18=[pos0:8+8=16->6(c1)|pos1:3+1+c1=5->5]=56|(56)+7*(15)+9*23\n",
      "Step 2: (56)+7*(15)+9*23 → 7*(15)=[part(5*7)=[pos0:7*5=35->5(c3)]=35|part(1*7)=[pos0:7*1=7->7]=70|sum(35,70)=[pos0:5+0=5->5|pos1:3+7=10->0(c1)]=105]=105|(56)+105+9*23\n",
      "Step 3: (56)+105+9*23 → 9*23=[part(3*9)=[pos0:9*3=27->7(c2)]=27|part(2*9)=[pos0:9*2=18->8(c1)]=180|sum(27,180)=[pos0:7+0=7->7|pos1:2+8=10->0(c1)|pos2:0+1+c1=2->2]=207]=207|(56)+105+207\n",
      "Step 4: (56)+105+207 → (56)+105=[pos0:6+5=11->1(c1)|pos1:5+0+c1=6->6|pos2:0+1=1->1]=161|161+207\n",
      "Step 5: 161+207 → 161+207=[pos0:1+7=8->8|pos1:6+0=6->6|pos2:1+2=3->3]=368|368\n",
      "Final result reached: 368. Halting.\n"
     ]
    }
   ],
   "source": [
    "predict_feedback(fb_model, dataset, '(38+18)+7*(15)+9*23')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "cce6d1e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9*9\n",
      "Step 2: (43)*9*9 → strip:(43)=43|43)*9*9\n",
      "Step 3: 43)*9*9 → 43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387*9\n",
      "Step 4: 387*9 → 387*9=[part(9*387)=[pos0:7*9=63->3(c6)|pos1:8*9+c6=78->8(c7)|pos2:3*9+c7=34->4(c3)]=3483]=3483|3483*9\n",
      "Step 5: 3483*9 → 3483*9=[part(9*3483)=[pos0:3*9=27->7(c2)|pos1:8*9+c2=74->4(c7)|pos2:4*9+c7=43->3(c4)|pos3:3*9+c4=31->1(c3)]=31347]=31347|31347\n",
      "Final result reached: 31347. Halting.\n"
     ]
    }
   ],
   "source": [
    "predict_feedback(fb_model, dataset, expr_to_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "4cfb68d3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13->3(c1)|pos1:3+0+c1=4->4]=43|(43)*9\n",
      "Step 2: (43)*9 → strip:(43)=43|43)*9\n",
      "Step 3: 43)*9 → 43)*9=[part(9*43)=[pos0:3*9=27->7(c2)|pos1:4*9+c2=38->8(c3)]=387]=387|387\n",
      "Final result reached: 387. Halting.\n"
     ]
    }
   ],
   "source": [
    "predict_feedback(fb_model, dataset, expr_to_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "db9d60ea",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/782 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---------------\n",
      "((912)+50*22*(94+2160))\n",
      "94+2160=[pos0:4+0=4->4|pos1:9+6=15->5(c1)|pos2:0+1+c1=2->2|pos3:0+2=2->2]=2254|((912)+50*22*(2254))\n",
      "---------------\n",
      "800+5238*54+40*50*61\n",
      "5238*54=[part(4*5238)=[pos0:8*4=32->2(c3)|pos1:3*4+c3=15->5(c1)|pos2:2*4+c1=9->9|pos3:5*4=20->0(c2)]=20952|part(5*5238)=[pos0:8*5=40->0(c4)|pos1:3*5+c4=19->9(c1)|pos2:2*5+c1=11->1(c1)|pos3:5*5+c1=26->6(c2)]=261900|sum(20952,261900)=[pos0:2+0=2->2|pos1:5+0=5->5|pos2:9+9=18->8(c1)|pos3:0+1+c1=2->2|pos4:2+6=8->8|pos5:0+2=2->2]=282852]=282852|800+282852+40*50*61\n",
      "---------------\n",
      "143640+201*38+9+86\n",
      "201*38=[part(8*201)=[pos0:1*8=8->8|pos1:0*8=0->0|pos2:2*8=16->6(c1)]=1608|part(3*201)=[pos0:1*3=3->3|pos1:0*3=0->0|pos2:2*3=6->6]=6030|sum(1608,6030)=[pos0:8+0=8->8|pos1:0+3=3->3|pos2:6+0=6->6|pos3:1+6=7->7]=7638]=7638|143640+7638+9+86\n",
      "---------------\n",
      "96+10+1900+73+64*62\n",
      "64*62=[part(2*64)=[pos0:4*2=8->8|pos1:6*2=12->2(c1)]=128|part(6*64)=[pos0:4*6=24->4(c2)|pos1:6*6+c2=38->8(c3)]=3840|sum(128,3840)=[pos0:8+0=8->8|pos1:2+4=6->6|pos2:1+8=9->9|pos3:0+3=3->3]=3968]=3968|96+10+1900+73+3968\n",
      "---------------\n",
      "((61+78)+(4620)*14+7+70+57)\n",
      "61+78=[pos0:1+8=9->9|pos1:6+7=13->3(c1)]=139|((139)+(4620)*14+7+70+57)\n",
      "---------------\n",
      "703824+235191600\n",
      "703824+235191600=[pos0:4+0=4->4|pos1:2+0=2->2|pos2:8+6=14->4(c1)|pos3:3+1+c1=5->5|pos4:0+9=9->9|pos5:7+1=8->8|pos6:0+5=5->5|pos7:0+3=3->3|pos8:0+2=2->2]=235895424|235895424\n",
      "---------------\n",
      "(6+37*(42*58)*64+87+(8+74))\n",
      "42*58=[part(8*42)=[pos0:2*8=16->6(c1)|pos1:4*8+c1=33->3(c3)]=336|part(5*42)=[pos0:2*5=10->0(c1)|pos1:4*5+c1=21->1(c2)]=2100|sum(336,2100)=[pos0:6+0=6->6|pos1:3+0=3->3|pos2:3+1=4->4|pos3:0+2=2->2]=2436]=2436|(6+37*(2436)*64+87+(8+74))\n",
      "---------------\n",
      "((13+88)+73*4)*((2064)*(88*55))\n",
      "88*55=[part(5*88)=[pos0:8*5=40->0(c4)|pos1:8*5+c4=44->4(c4)]=440|part(5*88)=[pos0:8*5=40->0(c4)|pos1:8*5+c4=44->4(c4)]=4400|sum(440,4400)=[pos0:0+0=0->0|pos1:4+0=4->4|pos2:4+4=8->8|pos3:0+4=4->4]=4840]=4840|((13+88)+73*4)*((2064)*(4840))\n",
      "---------------\n",
      "(340892)+(111+300)\n",
      "111+300=[pos0:1+0=1->1|pos1:1+0=1->1|pos2:1+3=4->4]=411|(340892)+(411)\n",
      "---------------\n",
      "4214+66+1152+69+39\n",
      "4214+66=[pos0:4+6=10->0(c1)|pos1:1+6+c1=8->8|pos2:2+0=2->2|pos3:4+0=4->4]=4280|4280+1152+69+39\n",
      "---------------\n",
      "19+85+53+935+516\n",
      "19+85=[pos0:9+5=14->4(c1)|pos1:1+8+c1=10->0(c1)]=104|104+53+935+516\n",
      "---------------\n",
      "((74*36)+19*76)*(60+51+95*92)\n",
      "74*36=[part(6*74)=[pos0:4*6=24->4(c2)|pos1:7*6+c2=44->4(c4)]=444|part(3*74)=[pos0:4*3=12->2(c1)|pos1:7*3+c1=22->2(c2)]=2220|sum(444,2220)=[pos0:4+0=4->4|pos1:4+2=6->6|pos2:4+2=6->6|pos3:0+2=2->2]=2664]=2664|((2664)+19*76)*(60+51+95*92)\n",
      "---------------\n",
      "68+84+333+3+405\n",
      "68+84=[pos0:8+4=12->2(c1)|pos1:6+8+c1=15->5(c1)]=152|152+333+3+405\n",
      "---------------\n",
      "(76+5+72*66*(60+90+78))\n",
      "60+90=[pos0:0+0=0->0|pos1:6+9=15->5(c1)]=150|(76+5+72*66*(150+78))\n",
      "---------------\n",
      "(930)+57+5980+88+56*6\n",
      "56*6=[part(6*56)=[pos0:6*6=36->6(c3)|pos1:5*6+c3=33->3(c3)]=336]=336|(930)+57+5980+88+336\n",
      "---------------\n",
      "(84*77*4+8+12*64*(8+48))\n",
      "8+48=[pos0:8+8=16->6(c1)|pos1:0+4+c1=5->5]=56|(84*77*4+8+12*64*(56))\n",
      "---------------\n",
      "48+70*18+94+1*53*(99*78)\n",
      "99*78=[part(8*99)=[pos0:9*8=72->2(c7)|pos1:9*8+c7=79->9(c7)]=792|part(7*99)=[pos0:9*7=63->3(c6)|pos1:9*7+c6=69->9(c6)]=6930|sum(792,6930)=[pos0:2+0=2->2|pos1:9+3=12->2(c1)|pos2:7+9+c1=17->7(c1)|pos3:0+6+c1=7->7]=7722]=7722|48+70*18+94+1*53*(7722)\n",
      "---------------\n",
      "(55*15+(27*13))*(74*34*(61*84))\n",
      "27*13=[part(3*27)=[pos0:7*3=21->1(c2)|pos1:2*3+c2=8->8]=81|part(1*27)=[pos0:7*1=7->7|pos1:2*1=2->2]=270|sum(81,270)=[pos0:1+0=1->1|pos1:8+7=15->5(c1)|pos2:0+2+c1=3->3]=351]=351|(55*15+(351))*(74*34*(61*84))\n",
      "---------------\n",
      "(80)+85+87*(74)+81*79\n",
      "87*74=[part(4*87)=[pos0:7*4=28->8(c2)|pos1:8*4+c2=34->4(c3)]=348|part(7*87)=[pos0:7*7=49->9(c4)|pos1:8*7+c4=60->0(c6)]=6090|sum(348,6090)=[pos0:8+0=8->8|pos1:4+9=13->3(c1)|pos2:3+0+c1=4->4|pos3:0+6=6->6]=6438]=6438|(80)+85+6438+81*79\n",
      "---------------\n",
      "(573300+3476+(68))\n",
      "573300+3476=[pos0:0+6=6->6|pos1:0+7=7->7|pos2:3+4=7->7|pos3:3+3=6->6|pos4:7+0=7->7|pos5:5+0=5->5]=576776|(576776+(68))\n",
      "---------------\n",
      "(2356+89+67)+((1207)+7905)\n",
      "2356+89=[pos0:6+9=15->5(c1)|pos1:5+8+c1=14->4(c1)|pos2:3+0+c1=4->4|pos3:2+0=2->2]=2445|(2445+67)+((1207)+7905)\n",
      "---------------\n",
      "25*61+61+11+(140)*99*38\n",
      "25*61=[part(1*25)=[pos0:5*1=5->5|pos1:2*1=2->2]=25|part(6*25)=[pos0:5*6=30->0(c3)|pos1:2*6+c3=15->5(c1)]=1500|sum(25,1500)=[pos0:5+0=5->5|pos1:2+0=2->2|pos2:0+5=5->5|pos3:0+1=1->1]=1525]=1525|1525+61+11+(140)*99*38\n",
      "---------------\n",
      "30*11+(114)*43*79+(7387)\n",
      "30*11=[part(1*30)=[pos0:0*1=0->0|pos1:3*1=3->3]=30|part(1*30)=[pos0:0*1=0->0|pos1:3*1=3->3]=300|sum(30,300)=[pos0:0+0=0->0|pos1:3+0=3->3|pos2:0+3=3->3]=330]=330|330+(114)*43*79+(7387)\n",
      "---------------\n",
      "((5*57)+73*71+(76+44)*57+24)\n",
      "5*57=[part(7*5)=[pos0:5*7=35->5(c3)]=35|part(5*5)=[pos0:5*5=25->5(c2)]=250|sum(35,250)=[pos0:5+0=5->5|pos1:3+5=8->8|pos2:0+2=2->2]=285]=285|((285)+73*71+(76+44)*57+24)\n",
      "---------------\n",
      "(63+58*(3886)+(131))\n",
      "58*3886=[part(6*58)=[pos0:8*6=48->8(c4)|pos1:5*6+c4=34->4(c3)]=348|part(8*58)=[pos0:8*8=64->4(c6)|pos1:5*8+c6=46->6(c4)]=4640|part(8*58)=[pos0:8*8=64->4(c6)|pos1:5*8+c6=46->6(c4)]=46400|part(3*58)=[pos0:8*3=24->4(c2)|pos1:5*3+c2=17->7(c1)]=174000|sum(348,4640,46400,174000)=[pos0:8+0+0+0=8->8|pos1:4+4+0+0=8->8|pos2:3+6+4+0=13->3(c1)|pos3:0+4+6+4+c1=15->5(c1)|pos4:0+0+4+7+c1=12->2(c1)|pos5:0+0+0+1+c1=2->2]=225388]=225388|(63+225388+(131))\n",
      "---------------\n",
      "(64+700*42+(8415)*85+37)\n",
      "700*42=[part(2*700)=[pos0:0*2=0->0|pos1:0*2=0->0|pos2:7*2=14->4(c1)]=1400|part(4*700)=[pos0:0*4=0->0|pos1:0*4=0->0|pos2:7*4=28->8(c2)]=28000|sum(1400,28000)=[pos0:0+0=0->0|pos1:0+0=0->0|pos2:4+0=4->4|pos3:1+8=9->9|pos4:0+2=2->2]=29400]=29400|(64+29400+(8415)*85+37)\n",
      "---------------\n",
      "(60+5+(4081))*85+71+(8370)\n",
      "60+5=[pos0:0+5=5->5|pos1:6+0=6->6]=65|(65+(4081))*85+71+(8370)\n",
      "---------------\n",
      "25+82+85*66*((91*94)*84*46)\n",
      "91*94=[part(4*91)=[pos0:1*4=4->4|pos1:9*4=36->6(c3)]=364|part(9*91)=[pos0:1*9=9->9|pos1:9*9=81->1(c8)]=8190|sum(364,8190)=[pos0:4+0=4->4|pos1:6+9=15->5(c1)|pos2:3+1+c1=5->5|pos3:0+8=8->8]=8554]=8554|25+82+85*66*((8554)*84*46)\n",
      "---------------\n",
      "(87)*88*59*(12+3)+5+12\n",
      "12+3=[pos0:2+3=5->5|pos1:1+0=1->1]=15|(87)*88*59*(15)+5+12\n",
      "---------------\n",
      "(4+76+(70*69))+(28*38*(26*54))\n",
      "70*69=[part(9*70)=[pos0:0*9=0->0|pos1:7*9=63->3(c6)]=630|part(6*70)=[pos0:0*6=0->0|pos1:7*6=42->2(c4)]=4200|sum(630,4200)=[pos0:0+0=0->0|pos1:3+0=3->3|pos2:6+2=8->8|pos3:0+4=4->4]=4830]=4830|(4+76+(4830))+(28*38*(26*54))\n",
      "---------------\n",
      "12*87+(58+70)*(11+392+24)\n",
      "58+70=[pos0:8+0=8->8|pos1:5+7=12->2(c1)]=128|12*87+(128)*(11+392+24)\n",
      "---------------\n",
      "7200*77+83+(6762)*62+55\n",
      "7200*77=[part(7*7200)=[pos0:0*7=0->0|pos1:0*7=0->0|pos2:2*7=14->4(c1)|pos3:7*7+c1=50->0(c5)]=50400|part(7*7200)=[pos0:0*7=0->0|pos1:0*7=0->0|pos2:2*7=14->4(c1)|pos3:7*7+c1=50->0(c5)]=504000|sum(50400,504000)=[pos0:0+0=0->0|pos1:0+0=0->0|pos2:4+0=4->4|pos3:0+4=4->4|pos4:5+0=5->5|pos5:0+5=5->5]=554400]=554400|554400+83+(6762)*62+55\n",
      "---------------\n",
      "14218958+(118)\n",
      "14218958+118=[pos0:8+8=16->6(c1)|pos1:5+1+c1=7->7|pos2:9+1=10->0(c1)|pos3:8+0+c1=9->9|pos4:1+0=1->1|pos5:2+0=2->2|pos6:4+0=4->4|pos7:1+0=1->1]=14219076|14219076\n",
      "---------------\n",
      "(87+12)*52*72*(55+88+(70*70))\n",
      "70*70=[part(0*70)=[pos0:0*0=0->0|pos1:7*0=0->0]=00|part(7*70)=[pos0:0*7=0->0|pos1:7*7=49->9(c4)]=4900|sum(00,4900)=[pos0:0+0=0->0|pos1:0+0=0->0|pos2:0+9=9->9|pos3:0+4=4->4]=4900]=4900|(87+12)*52*72*(55+88+(4900))\n",
      "---------------\n",
      "(408617+38+(121))\n",
      "408617+38=[pos0:7+8=15->5(c1)|pos1:1+3+c1=5->5|pos2:6+0=6->6|pos3:8+0=8->8|pos4:0+0=0->0|pos5:4+0=4->4]=408655|(408655+(121))\n",
      "---------------\n",
      "82+78+75+1+(167)+25+31\n",
      "82+78=[pos0:2+8=10->0(c1)|pos1:8+7+c1=16->6(c1)]=160|160+75+1+(167)+25+31\n",
      "---------------\n",
      "1377*(26)+80+56*46*28\n",
      "1377*26=[part(6*1377)=[pos0:7*6=42->2(c4)|pos1:7*6+c4=46->6(c4)|pos2:3*6+c4=22->2(c2)|pos3:1*6+c2=8->8]=8262|part(2*1377)=[pos0:7*2=14->4(c1)|pos1:7*2+c1=15->5(c1)|pos2:3*2+c1=7->7|pos3:1*2=2->2]=27540|sum(8262,27540)=[pos0:2+0=2->2|pos1:6+4=10->0(c1)|pos2:2+5+c1=8->8|pos3:8+7=15->5(c1)|pos4:0+2+c1=3->3]=35802]=35802|35802+80+56*46*28\n",
      "---------------\n",
      "(9*21)+29*88*((84+54)*3*44)\n",
      "84+54=[pos0:4+4=8->8|pos1:8+5=13->3(c1)]=138|(9*21)+29*88*((138)*3*44)\n",
      "---------------\n",
      "((58+82)+89*49*((31*19)+78+29))\n",
      "31*19=[part(9*31)=[pos0:1*9=9->9|pos1:3*9=27->7(c2)]=279|part(1*31)=[pos0:1*1=1->1|pos1:3*1=3->3]=310|sum(279,310)=[pos0:9+0=9->9|pos1:7+1=8->8|pos2:2+3=5->5]=589]=589|((58+82)+89*49*((589)+78+29))\n",
      "---------------\n",
      "(4900896)+63+60*69+27\n",
      "60*69=[part(9*60)=[pos0:0*9=0->0|pos1:6*9=54->4(c5)]=540|part(6*60)=[pos0:0*6=0->0|pos1:6*6=36->6(c3)]=3600|sum(540,3600)=[pos0:0+0=0->0|pos1:4+0=4->4|pos2:5+6=11->1(c1)|pos3:0+3+c1=4->4]=4140]=4140|(4900896)+63+4140+27\n",
      "---------------\n",
      "((95+7)+(21+61)*81*40*(35+68))\n",
      "95+7=[pos0:5+7=12->2(c1)|pos1:9+0+c1=10->0(c1)]=102|((102)+(21+61)*81*40*(35+68))\n",
      "---------------\n",
      "((86+9)*85*32+70*32*(31*58))\n",
      "31*58=[part(8*31)=[pos0:1*8=8->8|pos1:3*8=24->4(c2)]=248|part(5*31)=[pos0:1*5=5->5|pos1:3*5=15->5(c1)]=1550|sum(248,1550)=[pos0:8+0=8->8|pos1:4+5=9->9|pos2:2+5=7->7|pos3:0+1=1->1]=1798]=1798|((86+9)*85*32+70*32*(1798))\n",
      "---------------\n",
      "64896*26*80*16*79\n",
      "64896*26=[part(6*64896)=[pos0:6*6=36->6(c3)|pos1:9*6+c3=57->7(c5)|pos2:8*6+c5=53->3(c5)|pos3:4*6+c5=29->9(c2)|pos4:6*6+c2=38->8(c3)]=389376|part(2*64896)=[pos0:6*2=12->2(c1)|pos1:9*2+c1=19->9(c1)|pos2:8*2+c1=17->7(c1)|pos3:4*2+c1=9->9|pos4:6*2=12->2(c1)]=1297920|sum(389376,1297920)=[pos0:6+0=6->6|pos1:7+2=9->9|pos2:3+9=12->2(c1)|pos3:9+7+c1=17->7(c1)|pos4:8+9+c1=18->8(c1)|pos5:3+2+c1=6->6|pos6:0+1=1->1]=1687296]=1687296|1687296*80*16*79\n",
      "---------------\n",
      "(((76+86)*(33))*((50*99)*61*43))\n",
      "50*99=[part(9*50)=[pos0:0*9=0->0|pos1:5*9=45->5(c4)]=450|part(9*50)=[pos0:0*9=0->0|pos1:5*9=45->5(c4)]=4500|sum(450,4500)=[pos0:0+0=0->0|pos1:5+0=5->5|pos2:4+5=9->9|pos3:0+4=4->4]=4950]=4950|(((76+86)*(33))*((4950)*61*43))\n",
      "---------------\n",
      "(4681)+13*63+27*45\n",
      "13*63=[part(3*13)=[pos0:3*3=9->9|pos1:1*3=3->3]=39|part(6*13)=[pos0:3*6=18->8(c1)|pos1:1*6+c1=7->7]=780|sum(39,780)=[pos0:9+0=9->9|pos1:3+8=11->1(c1)|pos2:0+7+c1=8->8]=819]=819|(4681)+819+27*45\n",
      "---------------\n",
      "(94*87)*78*97*(55+94)+22+7\n",
      "94*87=[part(7*94)=[pos0:4*7=28->8(c2)|pos1:9*7+c2=65->5(c6)]=658|part(8*94)=[pos0:4*8=32->2(c3)|pos1:9*8+c3=75->5(c7)]=7520|sum(658,7520)=[pos0:8+0=8->8|pos1:5+2=7->7|pos2:6+5=11->1(c1)|pos3:0+7+c1=8->8]=8178]=8178|(8178)*78*97*(55+94)+22+7\n",
      "---------------\n",
      "(6994944)*(36)+(9+37)\n",
      "9+37=[pos0:9+7=16->6(c1)|pos1:0+3+c1=4->4]=46|(6994944)*(36)+(46)\n",
      "---------------\n",
      "(2160+(1902))\n",
      "2160+1902=[pos0:0+2=2->2|pos1:6+0=6->6|pos2:1+9=10->0(c1)|pos3:2+1+c1=4->4]=4062|(4062)\n",
      "---------------\n",
      "((164)+44*97*(79+85)*12*83)\n",
      "79+85=[pos0:9+5=14->4(c1)|pos1:7+8+c1=16->6(c1)]=164|((164)+44*97*(164)*12*83)\n",
      "---------------\n",
      "89*49*78+97+((140)+(5+43))\n",
      "5+43=[pos0:5+3=8->8|pos1:0+4=4->4]=48|89*49*78+97+((140)+(48))\n",
      "---------------\n",
      "((1713)+15*55*(94+22))\n",
      "94+22=[pos0:4+2=6->6|pos1:9+2=11->1(c1)]=116|((1713)+15*55*(116))\n",
      "---------------\n",
      "71*42*49*34*6+50*(79*69)\n",
      "79*69=[part(9*79)=[pos0:9*9=81->1(c8)|pos1:7*9+c8=71->1(c7)]=711|part(6*79)=[pos0:9*6=54->4(c5)|pos1:7*6+c5=47->7(c4)]=4740|sum(711,4740)=[pos0:1+0=1->1|pos1:1+4=5->5|pos2:7+7=14->4(c1)|pos3:0+4+c1=5->5]=5451]=5451|71*42*49*34*6+50*(5451)\n",
      "---------------\n",
      "1795+(2108)+98+44\n",
      "1795+2108=[pos0:5+8=13->3(c1)|pos1:9+0+c1=10->0(c1)|pos2:7+1+c1=9->9|pos3:1+2=3->3]=3903|3903+98+44\n",
      "---------------\n",
      "75*33*17+59+82+65+1*97\n",
      "75*33=[part(3*75)=[pos0:5*3=15->5(c1)|pos1:7*3+c1=22->2(c2)]=225|part(3*75)=[pos0:5*3=15->5(c1)|pos1:7*3+c1=22->2(c2)]=2250|sum(225,2250)=[pos0:5+0=5->5|pos1:2+5=7->7|pos2:2+2=4->4|pos3:0+2=2->2]=2475]=2475|2475*17+59+82+65+1*97\n",
      "---------------\n",
      "((31*35)*(89+85)+35+96*33*87)\n",
      "31*35=[part(5*31)=[pos0:1*5=5->5|pos1:3*5=15->5(c1)]=155|part(3*31)=[pos0:1*3=3->3|pos1:3*3=9->9]=930|sum(155,930)=[pos0:5+0=5->5|pos1:5+3=8->8|pos2:1+9=10->0(c1)]=1085]=1085|((1085)*(89+85)+35+96*33*87)\n",
      "---------------\n",
      "(69*75*67+78)+64+47+(40*73)\n",
      "69*75=[part(5*69)=[pos0:9*5=45->5(c4)|pos1:6*5+c4=34->4(c3)]=345|part(7*69)=[pos0:9*7=63->3(c6)|pos1:6*7+c6=48->8(c4)]=4830|sum(345,4830)=[pos0:5+0=5->5|pos1:4+3=7->7|pos2:3+8=11->1(c1)|pos3:0+4+c1=5->5]=5175]=5175|(5175*67+78)+64+47+(40*73)\n",
      "---------------\n",
      "316434+(316713)\n",
      "316434+316713=[pos0:4+3=7->7|pos1:3+1=4->4|pos2:4+7=11->1(c1)|pos3:6+6+c1=13->3(c1)|pos4:1+1+c1=3->3|pos5:3+3=6->6]=633147|633147\n",
      "---------------\n",
      "56*84+(67)+(1543)\n",
      "56*84=[part(4*56)=[pos0:6*4=24->4(c2)|pos1:5*4+c2=22->2(c2)]=224|part(8*56)=[pos0:6*8=48->8(c4)|pos1:5*8+c4=44->4(c4)]=4480|sum(224,4480)=[pos0:4+0=4->4|pos1:2+8=10->0(c1)|pos2:2+4+c1=7->7|pos3:0+4=4->4]=4704]=4704|4704+(67)+(1543)\n",
      "---------------\n",
      "(4347)+18564000+35*50\n",
      "35*50=[part(0*35)=[pos0:5*0=0->0|pos1:3*0=0->0]=00|part(5*35)=[pos0:5*5=25->5(c2)|pos1:3*5+c2=17->7(c1)]=1750|sum(00,1750)=[pos0:0+0=0->0|pos1:0+5=5->5|pos2:0+7=7->7|pos3:0+1=1->1]=1750]=1750|(4347)+18564000+1750\n",
      "---------------\n",
      "(89+97)+5+60+(78+36*32*44)\n",
      "36*32=[part(2*36)=[pos0:6*2=12->2(c1)|pos1:3*2+c1=7->7]=72|part(3*36)=[pos0:6*3=18->8(c1)|pos1:3*3+c1=10->0(c1)]=1080|sum(72,1080)=[pos0:2+0=2->2|pos1:7+8=15->5(c1)|pos2:0+0+c1=1->1|pos3:0+1=1->1]=1152]=1152|(89+97)+5+60+(78+1152*44)\n",
      "---------------\n",
      "(205+(174))\n",
      "205+174=[pos0:5+4=9->9|pos1:0+7=7->7|pos2:2+1=3->3]=379|(379)\n",
      "---------------\n",
      "(41+42+62+11+13+1386*60)\n",
      "1386*60=[part(0*1386)=[pos0:6*0=0->0|pos1:8*0=0->0|pos2:3*0=0->0|pos3:1*0=0->0]=0000|part(6*1386)=[pos0:6*6=36->6(c3)|pos1:8*6+c3=51->1(c5)|pos2:3*6+c5=23->3(c2)|pos3:1*6+c2=8->8]=83160|sum(0000,83160)=[pos0:0+0=0->0|pos1:0+6=6->6|pos2:0+1=1->1|pos3:0+3=3->3|pos4:0+8=8->8]=83160]=83160|(41+42+62+11+13+83160)\n",
      "---------------\n",
      "265696+18240+1\n",
      "265696+18240=[pos0:6+0=6->6|pos1:9+4=13->3(c1)|pos2:6+2+c1=9->9|pos3:5+8=13->3(c1)|pos4:6+1+c1=8->8|pos5:2+0=2->2]=283936|283936+1\n",
      "---------------\n",
      "17+41+(14+56)+(6+37)+(779)\n",
      "14+56=[pos0:4+6=10->0(c1)|pos1:1+5+c1=7->7]=70|17+41+(70)+(6+37)+(779)\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "loader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=0) \n",
    "pbar = tqdm(loader)\n",
    "for src, tgt in pbar:\n",
    "    for i in range(64):\n",
    "        print('---------------')\n",
    "        print(dataset.decode(src[i]))\n",
    "        print(dataset.decode(tgt[i]))\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "eed9adcc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[Feedback Inference Chain on '(38+5)*9']\n",
      "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13(c1)|pos1:3+0+c1=4]=43]=43|(43)*9\n",
      "Step 2: (43)*9 → 9*43=[part(3*9)=[pos0:9*3=27(c2)]=27|part(4*9)=[pos0:9*4=36(c3)]=360|sum(27,360)=[pos0:7+0=7|pos1:2+6=8|pos2:0+3=3]=387]=387|(43)*387\n",
      "Step 3: (43)*387 → strip:(43)*387)=43|43\n",
      "Final result reached: 43. Halting.\n"
     ]
    }
   ],
   "source": [
    "predict_feedback(fb_model, dataset, expr_to_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6db07ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "[Feedback Inference Chain on '(38+5)*9']\n",
    "Step 1: (38+5)*9 → 38+5=[pos0:8+5=13(c1)|pos1:3+0+c1=4]=43|(43)*9\n",
    "Step 2: (43)*9 → 9*9=[table:9*9=81]=81|(43)*81\n",
    "Step 3: (43)*81 → 43*81=[part(1*43)=[pos0:3*1=3|pos1:4*1=4]=43|part(8*43)=[pos0:3*8=24(c2)|pos1:4*8+c2=34(c3)]=3440|sum(43,3440)=[pos0:3+0=3|pos1:4+4=8|pos2:0+4=4|pos3:0+3=3]=3483]=3483|(3483\n",
    "Step 4: (3483 → strip:(34833)=3483|3483\n",
    "Final result reached: 3483. Halting.\n",
    "Epoch 15/1000: 100%|██████████| 782/782 [07:03<00:00,  1.85it/s, loss=0.014]"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "CX_AI",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
